{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Parte 2: Intro al Aprendizaje Federado\n",
    "\n",
    "En la sección previa, aprendimos sobre punteros de tensores (PointerTensors), que crean la infraestructura subyacente que necesitamos para preservar la privacidad con aprendizaje profundo. En esta sección, veremos cómo utilizar estas herramientas básicas para implementar nuestro primer algoritmo de aprendizaje profundo con preservación de privacidad, el aprendizaje federado. \n",
    "\n",
    "Autores:\n",
    "- Andrew Trask - Twitter: [@iamtrask](https://twitter.com/iamtrask)\n",
    "\n",
    "Traductores:\n",
    "- Arturo Márquez Flores - Twitter: [@arturomf94](https://twitter.com/arturomf94)\n",
    "- Ricardo Pretelt - Twitter: [@ricardopretelt](https://twitter.com/ricardopretelt)\n",
    "\n",
    "\n",
    "### ¿Qué es el aprendizaje federado?\n",
    "\n",
    "Es una manera simple y poderosa para entrenar modelos de aprendizaje profundo. Si piensas sobre datos de entrenamiento, siempre son el resultado de algún tipo de proceso de recolección. La gente (por medio de dispositivos) generan datos al registrar eventos del mundo real. Normalmente, estos datos son agregados a una ubicación central y única de tal manera que uno pueda entrenar un modelo de aprendizaje de máquina. El aprendizaje federado le da la vuelta a esto.\n",
    "\n",
    "En lugar de llevar los datos de entrenamiento al modelo (o servidor central), uno lleva el modelo a los datos de entraniento (donde sea que que esté).\n",
    "\n",
    "La idea es que esto la única copia permanente de los datos pertenece a quien los está creando, y así mantiene el control de su acceso. Cool, no?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2.1 Un Ejemplo de Aprendizaje Federado de Juguete \n",
    "\n",
    "Comencemos entrenando un modelo de juguete con el método centralizado. Este modelo es de lo más simple. Primero necesitamos:\n",
    "\n",
    "- Un conjunto de datos de juguete\n",
    "- Un modelo\n",
    "- Una lógica de entrenamiento básica para que el modelo se ajuste a los datos.\n",
    "\n",
    "Nota: Si esta API no te es familiar visita [fast.ai](http://fast.ai) y toma el curso antes de continuar con este tutorial."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "from torch import nn\n",
    "from torch import optim"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "# Un conjunto de datos de juguete\n",
    "data = torch.tensor([[0,0],[0,1],[1,0],[1,1.]], requires_grad=True)\n",
    "target = torch.tensor([[0],[0],[1],[1.]], requires_grad=True)\n",
    "\n",
    "# Un modelo de juguete.\n",
    "model = nn.Linear(2,1)\n",
    "\n",
    "def train():\n",
    "    # Lógica de entrenamiento\n",
    "    opt = optim.SGD(params=model.parameters(),lr=0.1)\n",
    "    for iter in range(20):\n",
    "\n",
    "        # 1) borra los gradientes previos (si existen)\n",
    "        opt.zero_grad()\n",
    "\n",
    "        # 2) haz una predicción\n",
    "        pred = model(data)\n",
    "\n",
    "        # 3) calcula cuál fue la pérdida\n",
    "        loss = ((pred - target)**2).sum()\n",
    "\n",
    "        # 4) encuentra los pesos que causaron la pérdida\n",
    "        loss.backward()\n",
    "\n",
    "        # 5) cambia esos pesos\n",
    "        opt.step()\n",
    "\n",
    "        # 6) imprime nuesto progreso\n",
    "        print(loss.data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¡Y ahí lo tienes! Hemos entrenado un modelo básico con un método convencional. Todos nuestros datos están agregados en nuestra máquina local y podemos usarlos para hacerle actualizaciones al modelo. El aprendizaje federado, sin embargo, no funciona así. Así que modifiquemos este ejemplo para hacerlo con aprendizaje federado. \n",
    "\n",
    "Lo que necesitamos:\n",
    "\n",
    "- Crear un par de trabajadores\n",
    "- Hacer que los punteros entrenen los datos en cada trabajador.\n",
    "- Actualizar la lógica de entrenamiento para que haga aprendizaje federado.\n",
    "\n",
    "    Nuevos Pasos de Entrenamiento:\n",
    "    - Mandar el modelo al trabajador correcto.\n",
    "    - Entrenarlo con los datos que se encuentran ahí.\n",
    "    - Recuperar el modelo y repetir con el próximo trabajador."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import syft as sy\n",
    "hook = sy.TorchHook(torch)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Crear un par de trabajadores\n",
    "\n",
    "bob = sy.VirtualWorker(hook, id=\"bob\")\n",
    "alice = sy.VirtualWorker(hook, id=\"alice\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Un conjunto de datos de juguete\n",
    "data = torch.tensor([[0,0],[0,1],[1,0],[1,1.]], requires_grad=True)\n",
    "target = torch.tensor([[0],[0],[1],[1.]], requires_grad=True)\n",
    "\n",
    "# Hacer que los punteros entrenen los datos en \n",
    "# cada trabajador mandando datos a bob y alice\n",
    "data_bob = data[0:2]\n",
    "target_bob = target[0:2]\n",
    "\n",
    "data_alice = data[2:]\n",
    "target_alice = target[2:]\n",
    "\n",
    "# Inicializar un modelo de juguete\n",
    "model = nn.Linear(2,1)\n",
    "\n",
    "data_bob = data_bob.send(bob)\n",
    "data_alice = data_alice.send(alice)\n",
    "target_bob = target_bob.send(bob)\n",
    "target_alice = target_alice.send(alice)\n",
    "\n",
    "# Organizar los punteros en una lista\n",
    "datasets = [(data_bob,target_bob),(data_alice,target_alice)]\n",
    "\n",
    "opt = optim.SGD(params=model.parameters(),lr=0.1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def train():\n",
    "    # Lógica de entrenamiento\n",
    "    opt = optim.SGD(params=model.parameters(),lr=0.1)\n",
    "    for iter in range(10):\n",
    "        \n",
    "        # NUEVO) Itera sobre el conjunto de datos de cada trabajador\n",
    "        for data,target in datasets:\n",
    "            \n",
    "            # NUEVO) Manda el modelo a el trabajador correcto\n",
    "            model.send(data.location)\n",
    "\n",
    "            # 1) borra los gradientes previos (si existen)\n",
    "            opt.zero_grad()\n",
    "\n",
    "            # 2) haz una predicción\n",
    "            pred = model(data)\n",
    "\n",
    "            # 3) calcula cuál fue la pérdida\n",
    "            loss = ((pred - target)**2).sum()\n",
    "\n",
    "            # 4) encuentra los pesos que causaron la pérdida\n",
    "            loss.backward()\n",
    "\n",
    "            # 5) cambia esos pesos\n",
    "            opt.step()\n",
    "            \n",
    "            # NUEVO recupera el modelo (con gradientes)\n",
    "            model.get()\n",
    "\n",
    "            # 6) imprime nuestro progreso\n",
    "            print(loss.get()) # NUEVO) Necesitamos llamar a .get()\n",
    "    \n",
    "# federated averaging"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ¡Muy bien!\n",
    "\n",
    "¡Y voilà! Ahora estamos entrenando un modelo de aprendizaje profundo muy simple utilizando aprendizaje federado. Mandamos el modelo a cada trabajador, generamos un nuevo gradiente, y luego recuperamos el gradiente en nuestro servidor local donde actualizamos nuestro modelo global. En ningún punto de este proceso vemos o requerimos acceso al conjunto de datos de entrenamiento subyacente. ¡Preservamos la privacidad de Bob y Alice!\n",
    "\n",
    "## Algunos defectos de este ejemplo\n",
    "\n",
    "Aunque este ejemplo es una buena introducción al aprendizaje federado, aún tiene algunos defectos importantes. Notablemente, cuando llamamos `model.get()` y recibimos el modelo actualizado de Bob o Alice de hecho podemos aprender mucho sobre el conjunto de entrenamiento de Bob/Alice sólo con la información de sus gradientes. ¡En algunos casos, podemos restaurar el conjunto de entrenamiento a la perfección!\n",
    "\n",
    "Así que, ¿qué podemos hacer? Bueno, la primera estrategia que utiliza la gente es **promediar el gradiente sobre múltiples individuos antes de actulizarlo en el servidor central**. Esta estrategia, sin embargo, requiere el uso de objetos PointerTensor más sofisticados. Entonces, en la siguiente sección nos tomaremos un tiempo para aprender un poco de la funcionalidad avanzada de los punteros y actualizaremos este ejemplo de aprendizaje federado.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# !Felicitaciones! - !Es hora de unirte a la comunidad!\n",
    "\n",
    "¡Felicitaciones por completar esta parte del tutorial! Si te gustó y quieres unirte al movimiento para preservar la privacidad, propiedad descentralizada de IA y la cadena de suministro de IA (datos), puedes hacerlo de las ¡siguientes formas!\n",
    "\n",
    "### Dale una estrella a PySyft en GitHub\n",
    "\n",
    "La forma más fácil de ayudar a nuestra comunidad es por darle estrellas a ¡los repositorios de Github! Esto ayuda a crear consciencia de las interesantes herramientas que estamos construyendo.\n",
    "\n",
    "- [Star PySyft](https://github.com/OpenMined/PySyft)\n",
    "\n",
    "### ¡Únete a nuestro Slack!\n",
    "\n",
    "La mejor manera de mantenerte actualizado con los últimos avances es ¡unirte a la comunidad! Tú lo puedes hacer llenando el formulario en [http://slack.openmined.org](http://slack.openmined.org)\n",
    "\n",
    "### ¡Únete a un proyecto de código!\n",
    "\n",
    "La mejor manera de contribuir a nuestra comunidad es convertirte en un ¡contribuidor de código! En cualquier momento puedes ir al _Github Issues_ de PySyft y filtrar por \"Proyectos\". Esto mostrará todos los tiquetes de nivel superior dando un resumen de los proyectos a los que ¡te puedes unir! Si no te quieres unir a un proyecto, pero quieres hacer un poco de código, también puedes mirar más mini-proyectos \"de una persona\" buscando por Github Issues con la etiqueta \"good first issue\".\n",
    "\n",
    "- [PySyft Projects](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3AProject)\n",
    "- [Good First Issue Tickets](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)\n",
    "\n",
    "### Donar\n",
    "\n",
    "Si no tienes tiempo para contribuir a nuestra base de código, pero quieres ofrecer tu ayuda, también puedes aportar a nuestro *Open Collective\"*. Todas las donaciones van a nuestro *web hosting* y otros gastos de nuestra comunidad como ¡hackathons y meetups!\n",
    "\n",
    "[OpenMined's Open Collective Page](https://opencollective.com/openmined)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
